package com.linln.admin.system.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 分布式锁的功能，主要用于全局互斥
 *
 * @author admin
 */
@Component
public class DistributeLockComponent implements InitializingBean {
    protected Logger log = LoggerFactory.getLogger("sys");

    @Autowired
    private DataSource dataSource;

    /**
     * 分布式锁
     */
    private static final String LOCK_TABLE = "sys_distribute_locks";

    private static Thread demean = null;// 整个系统中，只要这一个线程释放锁就够了。
    private static Thread updateDemean = null;
    /**
     * 每个锁的key及创建时间
     */
    public static final Map<String, Long> lockKeys = new ConcurrentHashMap<>();


    @Override
    public void afterPropertiesSet() throws Exception {
        if (demean != null) {
            return;
        }
        // 如果记录锁的表不存在，则创建，然后定时去清理过期的锁，达到释放锁的效果。
        createTables();
        demean = new Thread(() -> {

            while (true) {
                // 删除过期锁
                Connection conn = null;
                try {
                    conn = dataSource.getConnection();
                    long cnt = count(conn, "SELECT COUNT(*) FROM " + LOCK_TABLE + " WHERE expired_time < NOW()");
                    if (cnt > 0) {
                        execute(conn, "delete from " + LOCK_TABLE + " where expired_time < now()");
                    }
                } catch (Throwable e) {
                    log.error(e.getMessage(), e);
                } finally {
                    try {
                        conn.close();
                    } catch (Exception e) {
                    }
                }

                try {
                    Thread.sleep(7700);// 休息一下。取这个诡异的数字是为了防止更新与删除并发，导致死锁
                } catch (Exception e) {
                }
            }
        });
        demean.setName("auto_release_sys_distribute_locks_" + Thread.currentThread().getContextClassLoader().hashCode());
        demean.start();

        updateDemean = new Thread(() -> {
            //不断更新map中的锁信息
            while (true) {
                try {
                    if (lockKeys.size() > 0) {
                        List<String> delKeys = new ArrayList<>();
                        Iterator<String> keys = lockKeys.keySet().iterator();
                        while (keys.hasNext()) {
                            String key = keys.next();
                            Long time = lockKeys.get(key);
                            if (time == null) {
                                time = System.currentTimeMillis();
                            }
                            if (System.currentTimeMillis() - time > 2 * 3600 * 1000) {
                                //如果超过很长时间还没有释放，可能程序就坏掉了，直接释放
                                delKeys.add(key);
                                releaseLock(key);
                            } else {
                                updateExpiredTime(key, 20);//增加过期的时间
                            }
                        }

                        for (String key : delKeys) {
                            Long time = lockKeys.get(key);
                            if (time == null) {
                                time = System.currentTimeMillis();
                            }
                            if (System.currentTimeMillis() - time > 2 * 3600 * 1000) {
                                lockKeys.remove(key);
                            }
                        }
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                }
            }
        });

        updateDemean.setName("auto_update_sys_distribute_locks_" + Thread.currentThread().getContextClassLoader().hashCode());
        updateDemean.start();
    }

    /**
     * 尝试创建表
     */
    private void createTables() {
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            String sql = "select count(*) from " + LOCK_TABLE;
            try {
                count(conn, sql);
            } catch (Exception e) {// 如果查询语句都出错，说明没有表
                sql = "CREATE TABLE " + LOCK_TABLE + " ("//
                        + "  `lock_key` varchar(254) COLLATE utf8_bin NOT NULL," //
                        + "  `expired_time` datetime NOT NULL,"//
                        + "  PRIMARY KEY (`lock_key`),"//
                        + "  KEY idx_" + LOCK_TABLE + "_1 (`expired_time`)"//
                        + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='分布式锁记录'";
                execute(conn, sql);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
            }
        }
    }


    private void execute(Connection conn, String sql, Object... args) throws SQLException {
        PreparedStatement ps = null;
        try {
            ps = conn.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);
                }
            }
            ps.executeUpdate();
        } finally {
            try {
                if (ps != null) {
                    ps.close();
                }
            } catch (Exception e) {
            }
        }
    }


    private long count(Connection conn, String sql, Object... args) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(sql);
            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    ps.setObject(i + 1, args[i]);
                }
            }
            rs = ps.executeQuery();
            if (rs.next()) {
                return rs.getLong(1);
            }
        } finally {
            try {
                rs.close();
            } catch (Exception e) {
            }
            try {
                ps.close();
            } catch (Exception e) {
            }
        }
        return 0;
    }

    /**
     * 尝试加一个长时间的锁，直到程序自己释放为止
     *
     * @param lockKey 锁的名称
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean tryLock(Object lockKey) {
        boolean ret = tryLock(lockKey, 120);
        if (ret) {
            //如果加锁成功，就加入到更新队列中
            lockKeys.put(String.valueOf(lockKey), System.currentTimeMillis());
        }
        return ret;
    }

    /**
     * 尝试对指定的资源进行加锁，如果加锁成功，则返回true
     *
     * @param lockKey  锁的名称
     * @param idleTime 锁保留时长，单位：秒。说明：为什么不直接指定过期的Date，而指定时长？因为每台应用本身的时间可能互不一样，而且可能与数据库也不一样，会导致锁时间混乱，以致锁可能会无故失效。
     * @return 加锁成功，则返回true，否则返回null
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean tryLock(Object lockKey, long idleTime) {
        Connection conn = null;
        try {
            lockKey = String.valueOf(lockKey);
            conn = dataSource.getConnection();
            long count = count(conn, "select count(*) from " + LOCK_TABLE + " where lock_key=?", lockKey);
            if (count > 0) {
                // log.debug(lockKey + "重复提交！");
                return false;
            } else {
                execute(conn, "insert into " + LOCK_TABLE + "(lock_key,expired_time) values (?,DATE_ADD(NOW(),INTERVAL ? SECOND))", lockKey, idleTime);
                return true;
            }
        } catch (Exception e) {
            if (e.toString().indexOf("Duplicate entry ") < 0) {// 已经存在
                log.error(e.getMessage(), e);
            }
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
            }
        }
        // log.debug(lockKey + "重复提交！");
        return false;
    }


    /**
     * 更新锁的过期时间，表示要续锁，如果锁文件不存在，则新建一个锁
     *
     * @param lockKey  锁的名称
     * @param idleTime 锁保留时长，单位：秒。说明：为什么不直接指定过期的Date，而指定时长？因为每台应用本身的时间可能互不一样，而且可能与数据库也不一样，会导致锁时间混乱，以致锁可能会无故失效。
     * @return
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateExpiredTime(Object lockKey, long idleTime) {
        Connection conn = null;
        try {
            lockKey = String.valueOf(lockKey);
            conn = dataSource.getConnection();
            long count = count(conn, "select count(*) from " + LOCK_TABLE + " where lock_key=?", lockKey);
            if (count < 0) {
                tryLock(lockKey, idleTime);
            } else {
                execute(conn, "update " + LOCK_TABLE + " set expired_time =DATE_ADD(NOW(),INTERVAL ? SECOND) where lock_key=?", idleTime, lockKey);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
            }
        }
    }


    /**
     * 释放指定的锁
     *
     * @param lockKey
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void releaseLock(Object lockKey) {
        Connection conn = null;
        try {
            lockKey = String.valueOf(lockKey);
            conn = dataSource.getConnection();
            execute(conn, "delete from " + LOCK_TABLE + " where lock_key=?", lockKey);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                conn.close();
            } catch (Exception e) {
            }
        }
    }


}